//
// 管理 model
//

import { Model } from './model/models.js'

import all_modules from './addons_models/index.js'

import { Editmodel, EditX2m } from './model/editmodel.js'
import { Relation, One2many, Many2many } from './model/relation.js'

const module_helper = {
  get_one_depends(depends, modules) {
    const done = depends.reduce((acc, mod) => {
      if (mod in modules) {
        const pp = this.get_one_depends(modules[mod].depends || [], modules)
        acc = { ...acc, ...pp }
      }
      acc[mod] = null
      return acc
    }, {})

    return done
  },

  get_all_depends(modules) {
    const ms = Object.keys(modules).reduce((acc, mod) => {
      const ds = this.get_one_depends(modules[mod].depends || [], modules)
      acc[mod] = Object.keys(ds).filter(item => item in modules)
      return acc
    }, {})

    return ms
  },

  compute_rank(mod, ms) {
    if (!ms[mod].length) {
      return 0
    } else {
      const ranks = ms[mod].map(item => this.compute_rank(item, ms))
      return Math.max(...ranks) + 1
    }
  },

  get_depends_rank(modules) {
    const ms = this.get_all_depends(modules)
    const ms2 = Object.keys(ms).reduce((acc, mod) => {
      acc[mod] = this.compute_rank(mod, ms)
      return acc
    }, {})

    return ms2
  },

  get_modules_order(modules) {
    const ms = this.get_depends_rank(modules)
    // console.log('get_modules_order', ms)

    const ms2 = Object.keys(ms).sort((a, b) => ms[a] - ms[b])
    return ms2
  }
}

// const all_modules_order = module_helper.get_modules_order(all_modules)

function modules_order_get(modules_dict) {
  const modules = Object.keys(modules_dict)
    .reverse()
    .reduce((acc, cur) => {
      return { ...acc, ...modules_dict[cur] }
    }, {})

  return module_helper.get_modules_order(modules)
}

// console.log(all_modules, all_modules_order)

function Model_get(name, BaseModel, my_modules_order, modules_dict) {
  return Object.values(modules_dict).reduce((acc, one) => {
    return Model_get_one(name, acc, my_modules_order, one)
  }, BaseModel)
}

function Metadata_get(name, base_metadata, my_modules_order, modules_dict) {
  return Object.values(modules_dict).reduce((acc, one) => {
    return Metadata_get_one(name, acc, my_modules_order, one)
  }, base_metadata)
}

function Model_get_one(name, BaseModel, my_modules_order, my_modules) {
  const creators = my_modules_order
    .filter(mod => {
      const models = (my_modules[mod] || {}).models || {}
      return name in models && models[name].ModelCreator
    })
    .map(mod => (my_modules[mod] || {}).models[name].ModelCreator)

  if (creators.length) {
    const ModelNew = creators.reduce((acc, cur) => {
      acc = cur(acc)
      return acc
    }, BaseModel)
    return ModelNew
  } else {
    return BaseModel
  }
}

function Metadata_get_one(name, base_metadata, my_modules_order, my_modules) {
  return my_modules_order
    .filter(mod => {
      const models = (my_modules[mod] || {}).models || {}
      return name in models && models[name].metadata
    })
    .map(mod => (my_modules[mod] || {}).models[name].metadata)
    .reduce((acc, one) => {
      acc = { ...acc, ...one }
      return acc
    }, base_metadata)
}

export class Environment {
  constructor(payload = {}) {
    const { rpc } = payload
    this.rpc = rpc
  }

  get run_by_server() {
    return this.rpc.run_by_server
  }

  get uid() {
    return this.rpc.web.session.session_info.uid
  }

  get web() {
    return this.rpc.web
  }

  get user() {
    const info = this.rpc.web.session.session_info
    return {
      uid: info.uid,
      username: info.username,
      name: info.name
    }
  }

  get session() {
    return this.rpc.web.session
  }

  get server_version() {
    const session_info = this.rpc.web.session.session_info || {}
    const server_version_info = session_info.server_version_info || [0]
    const server_version = server_version_info[0]
    return server_version
  }

  get cids_local() {
    return this.web.session.cids_local
  }

  set cids_local(cids) {
    this.web.session.cids_local = cids
  }

  set_cids(cids) {
    this.cids_local = cids
  }

  get context() {
    const cids = this.cids_local
    const ctx = this.rpc.web.session.user_context
    const allowed_company_ids = cids
    return { ...ctx, allowed_company_ids }
  }

  load_session() {
    const info = this.web.session.session_info || {}
    const cids = this.cids_local
    return { ...info, cids }
  }

  async ref(xml_id) {
    const Model = this.model('ir.model.data')
    return Model.ref(xml_id)
  }

  editmodel(name, kw = {}) {
    return new Editmodel(name, { ...kw, env: this })
  }

  editx2m(name, kw = {}) {
    return new EditX2m(name, { ...kw, env: this })
  }

  relation(field_info) {
    return new Relation(field_info, { env: this })
  }

  one2many(field_info, kws = {}) {
    const that = this
    function info_get() {
      if (!('metadata' in field_info)) {
        return field_info
      }

      const modules_dict = {
        _odoojs_modules: all_modules,
        ...that.rpc._addons_models
      }

      const modules_order = modules_order_get(modules_dict)

      const metadata = Metadata_get(
        field_info.relation,
        {},
        modules_order,
        modules_dict
      )

      return {
        ...field_info,
        metadata: Object.keys(field_info.metadata).reduce((acc, fld) => {
          acc[fld] = { ...field_info.metadata[fld], ...(metadata[fld] || {}) }
          return acc
        }, {})
      }
    }

    const info = info_get()

    return new One2many(info, { ...kws, env: this })
  }

  many2many(field_info) {
    return new Many2many(field_info, { env: this })
  }

  model(name, kw = {}) {
    // 根据 addons_models 中所有模块的继承关系.
    //   获取 扩展的Model
    //   并 metadata
    // 扩展的Model仅 处理最基本的 odoo model 的修订
    // metadata  仅处理 domain 字符串
    // 就是说 addons_models 与 官方 odoo 的 models 做到一对一
    // addons_models 不包括 官方 odoo 在 xml 文件中的任何额外内容
    //
    // 修补 addons_models 中的内容:
    // 1. metadata 的修补. 查询 官方 odoo 的 models中的文件.
    // 11.  处理所有的 domain
    // 12.
    // 2. 执行 fields_get 后, 出现 bug后 自行修补
    // 3. 函数的 修补. 必要时修补
    //

    // console.log(this.rpc._addons_models)

    const modules_dict = {
      _odoojs_modules: all_modules,
      ...this.rpc._addons_models
    }

    const modules_order = modules_order_get(modules_dict)

    // console.log(name, all_modules_order1, order, modules_dict)
    const Model2 = Model_get(name, Model, modules_order, modules_dict)

    const metadata = Metadata_get(name, {}, modules_order, modules_dict)

    // console.log(name, metadata)

    return new Model2(name, { ...kw, env: this, metadata_extend: metadata })
  }
}
